
package whendone

/** Swing related helpers.
    @author Henrik Huttunen
 */
object SwingToolbox{
	import java.awt.Color
  import PrettyPrinter._
  import javax.swing.text._
  /** Sets given frame to the center of the screen */
  def centerFrame(frame: javax.swing.JFrame){
    import java.awt.Toolkit
    val toolkit = Toolkit.getDefaultToolkit();
    val x = ((toolkit.getScreenSize.getWidth-frame.getSize.getWidth)/2).toInt
    val y = ((toolkit.getScreenSize.getHeight-frame.getSize.getHeight)/2).toInt
    frame.setLocation(x, y)
  }
  /** Sets attributes using PrintingStyle. */
  def setStyleForAttributes(attributes: MutableAttributeSet, style: PrintingStyle): Unit = {    
    style match{
      case Fore(color) => StyleConstants.setForeground(attributes, color)
      case Back(color) => StyleConstants.setBackground(attributes, color)
      case Size(size) => StyleConstants.setFontSize(attributes, size)
      case Under => StyleConstants.setUnderline(attributes, true)
      case Italic => StyleConstants.setItalic(attributes, true)
      case Bold => StyleConstants.setBold(attributes, true)
      case Over => StyleConstants.setStrikeThrough(attributes, true)
      case Combined(styles @ _*) => styles foreach (style => setStyleForAttributes(attributes, style)) 
    }
  }  
}

/**
  Swing based IO. A bit hackish, should be refactored.
  @author Henrik Huttunen
*/
trait SwingIO extends IO{
  import CommandHistory._
  import PrettyPrinter._
  import javax.swing._
  import java.awt._
  import java.awt.event._
  
  private var commandHistory: History = CommandHistory.emptyHistory

  val frame = new JFrame  
  private val scrollBarSize = new Dimension(800, 700)
  private val input = new JTextField
  val output = new JTextPane //(rows, cols)  
  private val scrollbar = new JScrollPane(output, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS)

  // temporary hack for own use
  def setWidth(x: Int) = {
    scrollbar.setPreferredSize(new Dimension(x, scrollbar.getHeight))
    frame.pack
  }
  // temporary hack for own use
  def setHeight(x: Int) = {
    scrollbar.setPreferredSize(new Dimension(scrollbar.getWidth, x))
    frame.pack
  }

  val fontSize = 14
  val defaultFont = new Font("Courier New", Font.PLAIN, fontSize)

  output.setText(scala.List.make(100, "\n").mkString) // to get the current project to the bottom
  output.setFont(defaultFont)
  input.setFont(defaultFont)
  output.setBackground(Color.BLACK)
  output.setForeground(Color.WHITE)
  output.setEditable(false)
  input.setBackground(Color.WHITE)
  input.setForeground(Color.BLACK)
  input.setCaretColor(Color.BLACK)
  //output.getStyledDocument.setLineWrap(false)
 // output.setWrapStyleWord(true)
  
  scrollbar.setPreferredSize(scrollBarSize)
//  scrollbar.setMaximumSize(new Dimension(400, 300))  
  
  frame.setContentPane(new JPanel)
  frame.getContentPane.setLayout(new BoxLayout(frame.getContentPane, BoxLayout.Y_AXIS))
  frame.getContentPane.add(scrollbar)
  frame.getContentPane.add(input)
  frame.pack
  frame.setResizable(false)
  SwingToolbox.centerFrame(frame)
  
  frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE)  
  frame.setVisible(true)  
  input.grabFocus

  val typeCommand = "type command here"
  override def init = input.setText("")
  
  // defaultAttribues keep the original settings
  private val defaultAttributes = new text.SimpleAttributeSet(output.getInputAttributes.copyAttributes)
  private def freshAttributeSet = new text.SimpleAttributeSet(defaultAttributes.copyAttributes)
  
  /** Inserts text with attributes set using given function 'addAttributes'. */
  def insertText(message: String)(addAttributes: text.MutableAttributeSet => Unit): Unit = {
    print(message)
    //val attributes = output.getInputAttributes
    val attributes = freshAttributeSet
    addAttributes(attributes)
    val doc = output.getStyledDocument
    doc.setCharacterAttributes(doc.getLength - message.size, message.size, attributes, false)
  }
  /** Prints with given styles, e.g. printsWith("hello", Italic, Under) */
  def printWith(message: String)(styles: PrintingStyle*) = {
    insertText(message){ 
      attributes => 
      for(style <- styles)
        SwingToolbox.setStyleForAttributes(attributes, style)
    }
  }
  
  def setInputText(message: String, selectAll: Boolean) = {
    input.setText(message)
    if(selectAll) 
      input.selectAll 
    else {
      input.setCaretPosition(input.getText.size)
    }
  }
  def print(message: String) = {
    val doc = output.getDocument
    doc.insertString(doc.getLength, message, null)
    output.setCaretPosition(doc.getLength)
  }
  def printOverlined(message: String) = {
    insertText(message){ attributes =>
      text.StyleConstants.setStrikeThrough(attributes, true)
    }
  }
  def println(message: String) = print(message + "\n")
  def println(message: String, foreground: Color, background: Color) = {
    printWith(message + "\n")(Fore(foreground), Back(background))
  }
  
  // adds FocusListener, so that input shows typeCommand text when there's no focus on it
  input.addFocusListener(
    new java.awt.event.FocusListener{
      def focusLost(event: java.awt.event.FocusEvent) = {
        if(input.getText == ""){
          input.setText(typeCommand)
          input.setSelectedTextColor(Color.RED)
          input.selectAll
        }          
      }
      def focusGained(event: java.awt.event.FocusEvent) = {
        if(input.getText == typeCommand)
          input.setText("")
      }
    }
	)      

  def showCurrentProjectInHistory = {
    commandHistory.current match {
      case Some(command) =>
        setInputText(command, false)
      case None => setInputText("", false)
    }        
  }
  def moveHistoryBack = setCommandHistory(moveBackInCommandHistory(commandHistory))
  def moveHistoryForward = setCommandHistory(moveForwardInCommandHistory(commandHistory))
  
  /* Adds KeyListener with which you can browse the command history with up and down arrow keys.
     You can also use ESC to clear input text. 
  */
  input.addKeyListener(new KeyAdapter{
    override def keyReleased(e: KeyEvent) = {
      e.getKeyCode match {
        case KeyEvent.VK_UP =>
          moveHistoryBack
          showCurrentProjectInHistory
        case KeyEvent.VK_DOWN => 
          moveHistoryForward
          showCurrentProjectInHistory
        case KeyEvent.VK_ESCAPE => setInputText("", false)
        case _ =>
      }
    }
  })

  /** Sets the used command history. */
  def setCommandHistory(commandHistory: History) = synchronized {
    this.commandHistory = commandHistory
  }
  /** Reads a line from input. Hacky implementation. Refactor! */
  def readLine: String = {    
    import java.awt.event._
    
    // has the command been given (Enter pressed on input)
    var commandGiven = false
    
    // Gets the value of command with synchronization. 
    def giveCommand: Unit = {
      synchronized{commandGiven = true}
      synchronized{notifyAll}
      input.removeActionListener(actionListener)
    }
    // A temporary ActionListener waiting for Enter that is removed after use.
    lazy val actionListener = new ActionListener{
      def actionPerformed(event: ActionEvent) = {
        giveCommand
      }
    }
    input.addActionListener(actionListener)
 
    // Waits until command has been given.
    while(synchronized(!commandGiven)){
      try {synchronized(wait)} catch{ case _ => } 
    }
    val result = input.getText.substring(0, input.getText.size)
    println(result)
    input.setText("")
    result
  }
  /** Frees resources. */
  override def close = frame.dispose
}